﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "ALSound.h"

namespace UX_STL
{
namespace ALShell
{
	
//------------------------ ALSoundBuffer --------------------------//
/*
=================================================
	конструктор
=================================================
*/
	ALSoundBuffer::ALSoundBuffer():
		_uID(0), _fVolume(0.f), _fPitch(0.f), _fTime(0.f),
		_b3D(false), _bLooping(false), _eFormat(al_format::_UNKNOWN)
	{
	}

/*
=================================================
	деструктор
=================================================
*/
	ALSoundBuffer::~ALSoundBuffer()
	{
		if ( _uID != 0 ) {
			AL_CALL( alDeleteBuffers( 1, &_uID ) );
			_uID = 0;
		}

		_aSounds.Clear();
	}
	
/*
=================================================
	создать буфер
=================================================
*/
	bool ALSoundBuffer::Create(const void *pData, usize uDataSize, al_format::type eFormat, uint uFreq, float fVolume, float fPitch, bool bLooping)
	{
		CHECK_ERR( pData != null and uDataSize > 0 );

		uint	u_channels = 0,
				u_bit_per_sample = 0;

		CHECK_ERR( ALFormatInfo::GetInfo( eFormat, &u_channels, &u_bit_per_sample, null ) );
		//CHECK_ERR( u_channels == 1 and "only MONO formats supported from 3D sound" );

		Destroy();
		
		_fVolume	= fVolume;
		_fPitch		= fPitch;
		_bLooping	= bLooping;
		_eFormat	= eFormat;
		_fTime		= SafeDiv( (float)uDataSize, float( uFreq * u_channels * (u_bit_per_sample >> 3) ), 0.f );
		_b3D		= u_channels == 1;
		
		AL_CALL_R( alGenBuffers( 1, &_uID ) );
		AL_CALL_R( alBufferData( _uID, _eFormat, (const ALvoid *)pData, (ALsizei)uDataSize, uFreq ) );
		return true;
	}

/*
=================================================
	создать источник звука
=================================================
*/
	bool ALSoundBuffer::CreateSound(ALSoundPtr &pSound)
	{
		ALSoundPtr	p_sound = new ALSound();
		
		CHECK_ERR( p_sound->Create( this ) );
		pSound = p_sound;
		return true;
	}
	
/*
=================================================
	удалить буфер
=================================================
*/
	void ALSoundBuffer::Destroy()
	{
		if ( _uID != 0 ) {
			AL_CALL( alDeleteBuffers( 1, &_uID ) );
			_uID = 0;
		}

		_aSounds.Clear();
		
		_fTime		= 0.f;
		_fVolume	= 0.f;
		_fPitch		= 0.f;
		_bLooping	= false;
	}
	
/*
=================================================
	обновляет источники звука
=================================================
*/
	void ALSoundBuffer::_UpdateSounds()
	{
		FOR( i, _aSounds ) {
			AL_CALL( alSourcei( _aSounds[i]->_uID, AL_BUFFER, _uID ) );
		}
	}

/*
=================================================
	добавить звук в список
=================================================
*/
	void ALSoundBuffer::_AddSound(ALSound *pSound)
	{
		_aSounds.PushBack( pSound );
	}
	
/*
=================================================
	удалить звук из списка
=================================================
*/
	void ALSoundBuffer::_RemoveSound(ALSound *pSound)
	{
		FOR( i, _aSounds ) {
			if ( pSound == _aSounds[i] ) {
				_aSounds.FastErase( i );
				return;
			}
		}
	}



//--------------------------- ALSound ------------------------------//
/*
=================================================
	конструктор
=================================================
*/
	ALSound::ALSound():
		_uID(0), _pBuffer(null)
	{
	}

/*
=================================================
	деструктор
=================================================
*/
	ALSound::~ALSound()
	{
		if ( _uID != 0 ) {
			AL_CALL( alSourceStop( _uID ) );
			AL_CALL( alDeleteSources( 1, &_uID ) );
			_uID = 0;
		}
		if ( _pBuffer != null ) {
			_pBuffer->_RemoveSound( this );
			_pBuffer = null;
		}
	}

/*
=================================================
	создать источник звука
=================================================
*/
	bool ALSound::Create(ALuint uBuffer, float fVolume, float fPitch, bool bLooping)
	{
		CHECK_ERR( uBuffer != 0 );

		_Destroy();

		AL_CALL_R( alGenSources( 1, &_uID ) );

		float	a_temp[3] = { 0.f, 0.f, 0.f };

		AL_CALL( alSourcef ( _uID, AL_PITCH,	fPitch ) );
		AL_CALL( alSourcef ( _uID, AL_GAIN,		fVolume ) );
		AL_CALL( alSourcefv( _uID, AL_POSITION, a_temp ) );
		AL_CALL( alSourcefv( _uID, AL_VELOCITY, a_temp ) );
		AL_CALL( alSourcei ( _uID, AL_LOOPING,	bLooping ) );
		AL_CALL( alSourcei ( _uID, AL_BUFFER,	uBuffer ) );
		return true;
	}
	
/*
=================================================
	создать источник звука
=================================================
*/
	bool ALSound::Create(const ALSoundBufferPtr &pBuffer)
	{
		CHECK_ERR( pBuffer != null and pBuffer->_uID != 0 );

		_Destroy();

		AL_CALL_R( alGenSources( 1, &_uID ) );

		float	a_temp[3] = { 0.f, 0.f, 0.f };

		AL_CALL( alSourcef ( _uID, AL_PITCH,	pBuffer->Pitch() ) );
		AL_CALL( alSourcef ( _uID, AL_GAIN,		pBuffer->Volume() ) );
		AL_CALL( alSourcefv( _uID, AL_POSITION,	a_temp ) );
		AL_CALL( alSourcefv( _uID, AL_VELOCITY,	a_temp ) );
		AL_CALL( alSourcei ( _uID, AL_LOOPING,	pBuffer->IsLooping() ) );
		AL_CALL( alSourcei ( _uID, AL_BUFFER,	pBuffer->Id() ) );

		_pBuffer = pBuffer;
		_pBuffer->_AddSound( this );
		return true;
	}

/*
=================================================
	удалить источник звука
=================================================
*/
	bool ALSound::_Destroy()
	{
		if ( _uID != 0 ) {
			AL_CALL( alSourceStop( _uID ) );
			AL_CALL( alDeleteSources( 1, &_uID ) );
			_uID = 0;
		}
		if ( _pBuffer != null ) {
			_pBuffer->_RemoveSound( this );
			_pBuffer = null;
		}
		return true;
	}
	
/*
=================================================
	удалить объект
=================================================
*/
	void ALSound::_Release()
	{
		_Destroy();
		delete this;
	}
	



//-------------------------- ALFormatInfo -------------------------//

	struct TFormatInfo
	{
		al_format::type	eFmt;
		uint8			uChannels;
		uint8			uBitrate;
		bool			bFloat;
		bool			bCompressed;
	};

	static const TFormatInfo	s_aFormats[] =
	{
		{ al_format::CHN51_16, 0, 0 },
		
		{ al_format::MONO_8,			1,	8,	false,	false	},
		{ al_format::MONO_16,			1,	16,	false,	false	},
		{ al_format::MONO_F32,			1,	32,	true,	false	},
		{ al_format::MONO_F64,			1,	64,	true,	false	},
		{ al_format::MONO_MULAW,		1,	0,	false,	true	},
		{ al_format::MONO_IMA4,			1,	0,	false,	true	},
		{ al_format::MONO_16_ADPCM,		1,	16,	false,	true	},
		{ al_format::STEREO_8,			2,	8,	false,	false	},
		{ al_format::STEREO_16,			2,	16,	false,	false	},
		{ al_format::STEREO_F32,		2,	32,	true,	false	},
		{ al_format::STEREO_F64,		2,	64,	true,	false	},
		{ al_format::STEREO_MULAW,		2,	0,	false,	true	},
		{ al_format::STEREO_IMA4,		2,	0,	false,	true	},
		{ al_format::STEREO_16_ADPCM,	2,	16,	false,	true	},
		{ al_format::QUAD_8,			4,	8,	false,	false	},
		{ al_format::QUAD_16,			4,	16,	false,	false	},
		{ al_format::QUAD_F32,			4,	32,	true,	false	},
		{ al_format::QUAD_MULAW,		4,	0,	false,	true	},
		{ al_format::CHN51_8,			6,	8,	false,	false	},
		{ al_format::CHN51_16,			6,	16,	false,	false	},
		{ al_format::CHN51_F32,			6,	32,	true,	false	},
		{ al_format::CHN51_MULAW,		6,	0,	false,	true	},
		{ al_format::CHN61_8,			7,	8,	false,	false	},
		{ al_format::CHN61_16,			7,	16,	false,	false	},
		{ al_format::CHN61_F32,			7,	32,	true,	false	},
		{ al_format::CHN61_MULAW,		7,	0,	false,	true	},
		{ al_format::CHN71_8,			8,	8,	false,	false	},
		{ al_format::CHN71_16,			8,	16,	false,	false	},
		{ al_format::CHN71_F32,			8,	32,	true,	false	},
		{ al_format::CHN71_MULAW,		8,	0,	false,	true	},
		{ al_format::REAR_8,			1,	8,	false,	false	},
		{ al_format::REAR_16,			1,	16,	false,	false	},
		{ al_format::REAR_F32,			1,	32,	true,	false	},
		{ al_format::REAR_MULAW,		1,	0,	false,	true	},
		{ al_format::VORBIS,			0,	0,	false,	true	},
		{ al_format::WAVE,				0,	0,	false,	true	}
	};

/*
=================================================
	
=================================================
*/
	bool ALFormatInfo::GetInfo(al_format::type eFmt, uint *pChannels, uint *pBitrate, bool *pCompressed)
	{
		for (usize i = 0; i < CountOf( s_aFormats ); ++i)
		{
			if ( eFmt == s_aFormats[i].eFmt )
			{
				pChannels != null	?	*pChannels = s_aFormats[i].uChannels : 0;
				pBitrate != null		?	*pBitrate = s_aFormats[i].uBitrate : 0;
				pCompressed != null	?	*pCompressed = s_aFormats[i].bCompressed : 0;
				return true;
			}
		}
		return false;
	}

/*
=================================================
	
=================================================
*/
	bool ALFormatInfo::GetFormat(uint uChannels, uint uBitrate, bool bCompressed, al_format::type &eFormat)
	{
		for (usize i = 0; i < CountOf( s_aFormats ); ++i)
		{
			if ( uChannels == s_aFormats[i].uChannels and
				 uBitrate == s_aFormats[i].uBitrate and
				 bCompressed == s_aFormats[i].bCompressed )
			{
				eFormat = s_aFormats[i].eFmt;
				return true;
			}
		}
		return false;
	}

//-------------------------------------------------------------------

}	// ALShell
}	// UX_STL
